#include "config.h"

#include <stdint.h>
#include <errno.h>

#define DBG_SUBSYS S_LIBYLIB

#include "sysy_lib.h"
#include "longtask.h"
#include "squeue.h"
#include "dbg.h"

typedef struct {
        squeue_t queue;
        sy_rwlock_t rwlock;
        int count;
        uint64_t seq;
} longtask_t;

#define TASK_STATUS_SIZE 1024
#define TASK_MAX 10240

typedef struct {
        uint64_t id;
        int retval;
        char buf[TASK_STATUS_SIZE];
} entry_t;

static longtask_t *longtask;

static uint32_t __key_from_int(const void *i)
{
        //DINFO("key %llu\n", (LLU)*(uint64_t *)i);

        return *(uint64_t *)i;
}

static int __squeue_equal(const void *key, const void *data)
{
        const uint64_t *id = key;
        const squeue_entry_t *sent = data;
        entry_t *ent;

        ent = sent->ent;

        //DINFO("key %llu : %llu\n", (LLU)*id, (LLU)ent->id);

        return !(*id -  ent->id);
}

int longtask_init()
{
        int ret;

        YASSERT(longtask == NULL);

        ret = ymalloc((void *)&longtask, sizeof(longtask_t));
        if (unlikely(ret))
                GOTO(err_ret, ret);

        memset(longtask, 0x0, sizeof(longtask_t));

        ret = sy_rwlock_init(&longtask->rwlock, "longtask.rwlock");
        if (unlikely(ret))
                GOTO(err_free, ret);

        ret = squeue_init(&longtask->queue, TASK_MAX, __squeue_equal, __key_from_int);
        if (unlikely(ret))
                GOTO(err_free, ret);

        return 0;
err_free:
        yfree((void **)&longtask);
err_ret:
        return ret;
}

int longtask_insert(uint64_t *_handler, int retval, const char *desc)
{
        int ret;
        uint64_t id;
        entry_t *ent;

        if (strlen(desc) + 1 > TASK_STATUS_SIZE) {
                ret = EINVAL;
                GOTO(err_ret, ret);
        }

        ret = sy_rwlock_wrlock(&longtask->rwlock);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        if (longtask->count >= TASK_MAX) {
                ret = squeue_pop(&longtask->queue, (void **)&ent);
                if (unlikely(ret))
                        GOTO(err_lock, ret);

                DWARN("too many long task\n");

                longtask->count--;
                yfree((void **)&ent);
        }

        YASSERT(longtask->count < TASK_MAX);

        while (1) {
                id = longtask->seq ++;
                ret = squeue_get(&longtask->queue, &id, (void **)&ent);
                if (ret == ENOENT)
                        break;

                DWARN("too many long task\n");
        }

        ret = ymalloc((void **)&ent, sizeof(entry_t));
        if (unlikely(ret))
                GOTO(err_lock, ret);

        DINFO("insert task %llu\n", (LLU)id);

        ent->id = id;
        ent->retval = retval;
        strcpy(ent->buf, desc);

        ret = squeue_insert(&longtask->queue, &id, ent, 0);
        if (unlikely(ret))
                GOTO(err_lock, ret);

        longtask->count++;

        sy_rwlock_unlock(&longtask->rwlock);

        *_handler = id;

        return 0;
err_lock:
        sy_rwlock_unlock(&longtask->rwlock);
err_ret:
        return ret;
}

int longtask_update(uint64_t id, int retval, const char *desc)
{
        int ret;
        entry_t *ent;

        if (strlen(desc) + 1 > TASK_STATUS_SIZE) {
                ret = EINVAL;
                GOTO(err_ret, ret);
        }

        ret = sy_rwlock_wrlock(&longtask->rwlock);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = squeue_get(&longtask->queue, &id, (void **)&ent);
        if (unlikely(ret))
                GOTO(err_lock, ret);

        ent->retval = retval;
        strcpy(ent->buf, desc);

        sy_rwlock_unlock(&longtask->rwlock);

        return 0;
err_lock:
        sy_rwlock_unlock(&longtask->rwlock);
err_ret:
        return ret;
}

int longtask_get(uint64_t id, int *retval, char *desc)
{
        int ret;
        entry_t *ent;

        ret = sy_rwlock_rdlock(&longtask->rwlock);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = squeue_get(&longtask->queue, &id, (void **)&ent);
        if (unlikely(ret))
                GOTO(err_lock, ret);

        *retval = ent->retval;
        strcpy(desc, ent->buf);

        sy_rwlock_unlock(&longtask->rwlock);

        return 0;
err_lock:
        sy_rwlock_unlock(&longtask->rwlock);
err_ret:
        return ret;
}

int longtask_drop(uint64_t id)
{
        int ret;
        entry_t *ent;

        ret = sy_rwlock_wrlock(&longtask->rwlock);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = squeue_remove(&longtask->queue, &id, (void **)&ent);
        if (unlikely(ret)) {
                DERROR("drop %llu ret (%d) %s\n", (LLU)id, ret, strerror(ret));
                GOTO(err_lock, ret);
        }

        longtask->count--;
        yfree((void **)&ent);

        sy_rwlock_unlock(&longtask->rwlock);

        return 0;
err_lock:
        sy_rwlock_unlock(&longtask->rwlock);
err_ret:
        return ret;
}
